const fs = require('fs-extra')
const path = require('path')
const exec = require('child_process').exec
const userid = require('userid')
const mm = require('micromatch')
const mkdirp = require('mkdirp')
const now = require('./now')
const PluginResolve = require('./pluginResolve').PluginResolve
const PluginError = require('./pluginResolve').PluginError

function existFunc (value, funcName, paramName) {
  try {
    userid[funcName](value)
    return true
  } catch (err) {
    if (err.message === `${paramName} not found`) {
      return false
    } else {
      throw err
    }
  }
}

const exists = {
  uid: function (uid) {
    return existFunc(uid, 'username', 'uid')
  },
  gid: function (gid) {
    return existFunc(gid, 'groupname', 'gid')
  },
  username: function (username) {
    return existFunc(username, 'uid', 'username')
  },
  groupname: function (groupname) {
    return existFunc(groupname, 'gid', 'groupname')
  }
}

function resolveUserId (options) {
  let { user, name, idFunc, nameFunc } = options
  if (user === undefined) {
    throw new ConfigError(`${name}是必配项`)
  }
  if (typeof (user) === 'number') {
    if (exists[idFunc](user)) {
      return userid[nameFunc](user)
    } else {
      throw new ConfigError(`${name}不存在`)
    }
  } else if (typeof (user) === 'string') {
    if (exists[nameFunc](user)) {
      return userid[idFunc](user)
    } else {
      throw new ConfigError(`${name}不存在`)
    }
  } else {
    throw new ConfigError(`${name}必须是字符串或数字类型`)
  }
}

function resolvePath (base, child, options = {}) {
  if (child === undefined) {
    if (options.childRequired) {
      return { error: options.childMissingError }
    } else {
      return {}
    }
  }
  if (child[0] === '/') {
    return { path: child }
  }
  if (base === undefined) {
    return { error: options.baseMissingError }
  }
  if (base[0] !== '/') {
    return { error: options.baseRelativeError }
  }
  return { path: path.resolve(base, child) }
}

class ConfigError extends Error { }

class Config {
  constructor (path, obj) {
    try {
      this._path = path
      this._set(obj)
      this._prepare()
    } catch (err) {
      if (err instanceof ConfigError) {
        this.error = err.message
      } else {
        throw err
      }
    }
  }

  branchMatch (branch) {
    return mm.isMatch(branch, this._branches)
  }

  exec () {
    let tNow = now()

    let command = exec(this._command, {
      cwd: this._workingDirectory,
      uid: this._uid,
      gid: this._gid
    })

    if (this._logFile) {
      let logger = fs.createWriteStream(this._logFile, { flags: 'a' })
      logger.write(`新的命令执行 \`${this._command}\` at ${tNow}\n`)
      command.stdout.on('data', (data) => {
        logger.write(data)
      })
      command.stderr.on('data', (data) => {
        logger.write(data)
      })
      command.on('close', (code) => {
        if (code !== 0) {
          logger.write(`| Command exit with error code ${code}\n`)
        } 
        logger.end('\n')
      })
    }
  }

  _set (config) {
    this._setPlugin(config.plugin)
    this._setToken(config.token)
    this._setBranches(config.branches)
    this._setUid(config.user)
    this._setGid(config.group)
    this._setWorkingDirectory(config.workingDirectoryBase, config.workingDirectory)
    this._setCommand(config.command, config.commandFileBase, config.commandFile)
    this._setLogFile(config.logFileBase, config.logFile)
  }

  _setPlugin (pluginName) {
    if (pluginName) {
      try {
        this.normalizeData = PluginResolve(pluginName)
      } catch (err) {
        if (err instanceof PluginError) {
          throw new ConfigError(err.message)
        }
      }
    } else {
      throw new ConfigError('必须指定一个插件')
    }
  }

  _setToken (token) {
    if (token) {
      this.token = token
    }
  }

  _setBranches (branches) {
    this._branches = branches || '*'
  }

  _setUid (user) {
    this._uid = resolveUserId({
      user: user,
      name: 'user',
      idFunc: 'uid',
      nameFunc: 'username'
    })
  }

  _setGid (group) {
    this._gid = resolveUserId({
      user: group,
      name: 'group',
      idFunc: 'gid',
      nameFunc: 'groupname'
    })
  }

  _setWorkingDirectory (workingDirectoryBase, workingDirectory) {
    workingDirectory = workingDirectory || this._path
    let resolved = resolvePath(workingDirectoryBase, workingDirectory, {
      childRequired: true,
      childMissingError: 'workingDirectory必须得配置',
      baseMissingError: 'workingDirectory配置成相对路径，但没有给出workingDirectoryBase',
      baseRelativeError: 'workingDirectoryBase必须配置成绝对路径'
    })
    if (resolved.error) {
      throw new ConfigError(resolved.error)
    } else {
      this._workingDirectory = resolved.path
    }
  }

  _setCommand (command, commandFileBase, commandFile) {
    if (command) {
      this._command = command
    } else {
      commandFile = commandFile || `${this._path}`
      let resolved = resolvePath(commandFileBase, commandFile, {
        childRequired: true,
        childMissingError: 'command和commandFile必须配置其一',
        baseMissingError: 'commandFile配置成相对路径，但没有给出commandFileBase',
        baseRelativeError: 'commandBase必须配置成绝对路径'
      })
      if (resolved.error) {
        throw new ConfigError(resolved.error)
      } else {
        this._command = resolved.path
      }
    }
  }

  _setLogFile (logFileBase, logFile) {
    logFile = logFile === true ? `${this._path}.log` : logFile
    let resolved = resolvePath(logFileBase, logFile, {
      baseMissingError: 'logFile配置成相对路径，但没有给出logFileBase',
      baseRelativeError: 'logFileBase必须配置成绝对路径'
    })
    if (resolved.error) {
      throw new ConfigError(resolved.error)
    } else if (resolved.path) {
      this._logFile = resolved.path
    }
  }

  _prepare () {
    let logFile = this._logFile
    if (logFile && !fs.existsSync(logFile)) {
      let dir = path.dirname(logFile)
      if (!fs.existsSync(dir)) {
        throw new ConfigError('日志文件所在的目录不存在')
      }
      fs.ensureFileSync(logFile)
      fs.chmodSync(logFile, '0644')
      fs.chownSync(logFile, this._uid, this._gid)
    }
  }

}

module.exports = Config
